
"""\
It provides the generic conversion of Exif (Exchangeable Image File) \
format version 2.2 based on th specified on \
<a href="http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf">
http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf</a>.
"""

__version__ = '2.2-r4'

__aof__ = ('Reco Exif', 'EXF')

# Exif tag offset refers to the TIFF header. Exif header is the extra 6 bytes
# added to the offset for the imported data/file.
_EXIF_HEADER_LENGTH = 6


def __exifGetString(tag, tp, target, source, size, **kws):
    st = target.readString(source, size, **kws)
    size_ = len(st) + 1

    return st, size_, __newItemObject(target, 'value', st, size_, **kws)


def __exitComponentConfiguration(tag, tp, target, source, size, **kws):
    va = ''
    for c in target.readString(source, size, **kws):
        va += tag['maps'].get(ord(c), '-')

    return va, size, __newItemObject(target, 'value', va, size, **kws)


_EXIT_TAGS = {
    0x100: {'name': 'ImageWidth'},
    0x101: {'name': 'ImageLength'},
    0x102: {'name': 'BitsPerSample'},
    0x103: {
        'name': 'Compression',
        'maps': {
            1: 'uncompressed',    2: 'CCITT 1D',
            3: 'T4/Group 3 Fax',  4: 'T6/Group 4 Fax',
            5: 'LZW',             6: 'JPEG compression',
            7: 'JPEG',            8: 'Adobe Deflate',
            9: 'JBIG B&W',        10: 'JBIG Color',
            99: 'JPEG',           262: 'Kodak 262',
            32766: 'Next',        32767: 'Sony ARW Compressed',
            32769: 'Packed RAW',  32770: 'Samsung SRW Compressed',
            32771: 'CCIRLEW',     32772: 'Samsung SRW Compressed 2',
            32773: 'PackBits',    32809: 'Thunderscan',
            32867: 'Kodak KDC Compressed',
            32895: 'IT8CTPAD',    32896: 'IT8LW',
            32897: 'IT8MP',       32898: 'IT8BL',
            32908: 'PixarFilm',   32909: 'PixarLog',
            32946: 'Deflate',     32947: 'DCS',
            34661: 'JBIG',        34676: 'SGILog',
            34677: 'SGILog24',    34712: 'JPEG2000',
            34713: 'Nikon NEF Compressed',
            34715: 'JBIG2 TIFF FX',
            34718: 'Microsoft Document Imaging (MDI) Binary Level Codec',
            34719: 'Microsoft Document Imaging (MDI) Progressive Transform ' \
                   'Codec',
            34720: 'Microsoft Document Imaging (MDI) Vector',
            34892: 'Lossy JPEG',
            65000: 'Kodak DCR Compressed',
            65535: 'Pentax PEF Compressed'
        }},
    0x106: {
        'name': 'PhotometricInterpretation',
        'maps': {
            2: 'RGB',
            6: 'YCbCr'
        }},
    0x107: {
        'name': 'Thresholding',
        'maps': {
            1: 'No dithering or halfoning',
            2: 'Ordered dither or halfone',
            3: 'Randomized dither'
        }},
    0x108: {'name': 'CellWidth'},
    0x109: {'name': 'CellLength'},
    0x10a: {
        'name': 'FillOrder',
        'maps': {
            1: 'Normal',
            2: 'Reversed'
        }},
    0x10d: {'name': 'DocumentName'},
    0x10e: {'name': 'ImageDescription'},
    0x10f: {'name': 'Make'},
    0x110: {'name': 'Model'},
    0x111: {'name': 'StripOffsets'},
    0x112: {
        'name': 'Orientation',
        'maps': {
            1: 'top-left',
            2: 'top-right',
            3: 'bottom-right',
            4: 'bottom-left',
            5: 'left-top',
            6: 'right-top',
            7: 'right-bottom',
            8: 'left-bottom'
        }},
    0x115: {'name': 'SamplesPerPixel'},
    0x116: {'name': 'RowsPerStrip'},
    0x117: {'name': 'StripByteCounts'},
    0x118: {'name': 'MinSampleValue'},
    0x119: {'name': 'MaxSmapleValue'},
    0x11a: {'name': 'XResolution'},
    0x11b: {'name': 'YResolution'},
    0x11c: {
        'name': 'PlanarConfiguration',
        'maps': {
            1: 'Chunky Format',
            2: 'Planar Format'
        }},
    0x11d: {'name': 'PageName'},
    0x11e: {'name': 'XPosition'},
    0x11f: {'name': 'YPosition'},
    0x120: {'name': 'FreeOffsets'},
    0x121: {'name': 'FreeByteCounts'},
    0x122: {
        'name': 'GrayResponseUnit',
        'maps': {
            1: '0.1',   2: '0.001', 3: '0.0001',
            4: '1e-05', 5: '1e-06'
        }},
    0x123: {'name': 'GrayResponseCurve'},
    0x124: {'name': 'T4Options'},
    0x125: {'name': 'T6Options'},
    0x128: {
        'name': 'ResolutionUnit',
        'maps': {
            2: 'inch',
            3: 'cm'
        }},
    0x129: {'name': 'PageNumber'},
    0x12c: {'name': 'ColorResponseUnit'},
    0x12d: {'name': 'TransferFunction'},
    0x131: {'name': 'Software'},
    0x132: {'name': 'DateTime'},
    0x13b: {'name': 'Artist'},
    0x13c: {'name': 'Hostcomputer'},
    0x13d: {
        'name': 'Predictor',
        'maps': {
            1: 'None',
            2: 'Horizontal differencing'
        }},
    0x13e: {'name': 'WhitePoint'},
    0x13f: {'name': 'PrimaryChromaticities'},
    0x140: {'name': 'ColorMap'},
    0x141: {'name': 'HalftoneHints'},
    0x142: {'name': 'TileWidth'},
    0x143: {'name': 'TileLength'},
    0x144: {'name': 'TileOffsets'},
    0x145: {'name': 'TileByteCounts'},
    0x146: {'name': 'BadFaxLines'},
    0x147: {'name': 'CleanFaxData'},
    0x148: {'name': 'ConsecultiveBadFaxLines'},
    0x14c: {
        'name': 'lnkSet',
        'maps': {
            1: 'CMYK',
            2: 'Not CMYK'
        }},
    0x14d: {'name': 'lnkNames'},
    0x14e: {'name': 'Numberoflnks'},
    0x150: {'name': 'DotRange'},
    0x151: {'name': 'TargetPrinter'},
    0x152: {
        'name': 'ExtraSamples',
        'maps': {
            0: 'Unspecified',
            1: 'Associated Alpha',
            2: 'Unassociated Alpha'
        }},
    0x153: {
        'name': 'SampleFormat',
        'maps': {
            1: 'Unsigned',  2: 'Signed',      3: 'Float',
            4: 'Undefined', 5: 'Complex int', 6: 'Complex float'
        }},
    0x154: {'name': 'SMinSampleValue'},
    0x155: {'name': 'SMaxSampleValue'},
    0x156: {'name': 'TransferRange'},
    0x157: {'name': 'ClipPath'},
    0x158: {'name': 'XClipPathUnits'},
    0x159: {'name': 'YClipPathUnits'},
    0x15a: {
        'name': 'Indexed',
        'maps': {
            0: 'Not indexed',
            1: 'Indexed'
        }},
    0x15b: {'name': 'JPEGTables'},
    0x15f: {
        'name': 'OPIProxy',
        'maps': {
            0: 'Higher resolution image does not exist',
            1: 'Higher resolution image exists'
        }},
    0x191: {
        'name': 'ProfileType',
        'maps': {
            0: 'Unspecified',
            1: 'Group 3 FAX'
        }},
    0x192: {
        'name': 'FaxProfile',
        'maps': {
            0: 'Unknown',
            1: 'Minimal B&W lossless, S',
            2: 'Extended B&W lossless, F',
            3: 'Lossless JBIG B&W, J',
            4: 'Lossy color and grayscale, C',
            5: 'Lossless color and grayscale, L',
            6: 'Mixed raster content, M',
            7: 'Profile T',
            255: 'Multiple Profiles'
        }},
    0x193: {'name': 'CodingMethods'},
    0x194: {'name': 'VersionYear'},
    0x195: {'name': 'ModeNumber'},
    0x1b1: {'name': 'Decode'},
    0x1b2: {'name': 'DefaultImageColor'},
    0x1b3: {'name': 'T82Options'},
    0x1b5: {'name': 'JPEGTables'},
    0x200: {
        'name': 'JPEGProc',
        'maps': {
            1: 'Baseline',
            2: 'Lossless'
        }},
    0x201: {'name': 'JPEGInterchangeFormat'},
    0x202: {'name': 'JPEGInterchangeFormatLength'},
    0x203: {'name': 'JPEGRestartInterval'},
    0x205: {'name': 'JPEGLosslessPredictors'},
    0x206: {'name': 'JEPGPointTransforms'},
    0x207: {'name': 'JPEGQTables'},
    0x208: {'name': 'JPEGDCTables'},
    0x209: {'name': 'JPEGACTables'},
    0x211: {'name': 'YCbCrCoefficients'},
    0x212: {'name': 'YCbCrSubSampling'},
    0x213: {
        'name': 'YCbCrPositioning',
        'maps': {
            1: 'Centered',
            2: 'co-sited'
        }},
    0x214: {'name': 'ReferenceBlackWhite'},
    0x22f: {'name': 'StripRowcounts'},
    0x2bc: {'name': 'ApplicationNotes'},
    0x3e7: {'name': 'USPTOMiscellaneous'},
    0x1000: {'name': 'RelatedImageFileFormat'},
    0x1001: {'name': 'RelatedImageWidth'},
    0x1002: {'name': 'RelatedImageHeight'},
    0x4746: {'name': 'Rating'},
    0x4747: {'name': 'XP_DIP_XML'},
    0x4748: {'name': 'StitchInfo'},
    0x4749: {'name': 'RatingPercent'},
    0x800d: {'name': 'ImageId'},
    0x827d: {'name': 'Model2'},
    0x828f: {'name': 'BatteryLevel'},
    0x8290: {
        'name': 'KodakIFD',
        'ifd': {
            0x001: {'name': 'UnknownEV?'},
            0x003: {'name': 'ExposureValue'},
            0x3e9: {'name': 'OriginalFileName'},
            0x3eb: {'name': 'SensorLeftBorder'},
            0x3ec: {'name': 'SensorTopBorder'},
            0x3ed: {'name': 'SensorImageWidth'},
            0x3ee: {'name': 'SensorImageHeight'},
            0x3f1: {'name': 'TextualInfo'},
            0x3fc: {'name': 'WhiteBalance'},
            0x3fd: {'name': 'Processing'},
            0x401: {'name': 'Time'},
            0x406: {'name': 'CameraTemperature'},
            0x407: {'name': 'AdapterVoltage'},
            0x408: {'name': 'BatteryVoltage'},
            0x414: {'name': 'NCDFileInfo'},
            0x846: {'name': 'ColorTemperature'},
            0x848: {'name': "WB_RGBLevelsDaylight"},
            0x849: {'name': "WB_RGBLevelsTungsten"},
            0x84a: {'name': "WB_RGBLevelsFluorescent"},
            0x84b: {'name': "WB_RGBLevelsFlash"},
            0x84c: {'name': "WB_RGBLevelsCustom"},
            0x84d: {'name': "WB_RGBLevelsAuto"},
            0x852: {'name': "WB_RGBMul0"},
            0x853: {'name': "WB_RGBMul1"},
            0x854: {'name': "WB_RGBMul2"},
            0x855: {'name': "WB_RGBMul3"},
            0x85c: {'name': "WB_RGBCoeffs0"},
            0x85d: {'name': "WB_RGBCoeffs1"},
            0x85e: {'name': "WB_RGBCoeffs2"},
            0x85f: {'name': "WB_RGBCoeffs3"},
            0x903: {'name': "BaseISO"},
            0x9ce: {'name': "SensorSerialNumber"},
            0xce5: {'name': "FirmwareVersion"},
            0xe4c: {'name': "KodakLook"},
            0x1389: {'name': "InputProfile"},
            0x138a: {'name': "KodakLookProfile"},
            0x138b: {'name': "OutputProfile"},
            0x1391: {'name': "ToneCurveFileName"}
        }},
    0x8298: {'name': 'Copyright'},
    0x829a: {'name': 'ExposureTime'},
    0x829d: {'name': 'FNumber'},
    0x83bb: {'name': 'IPTC-NAA'},
    0x8769: {
        'name': 'Exif IFD Pointer',
        'ifd': {
            0x8822: {
                'name': 'ExposureProgram',
                'maps': {
                    0: 'Not Defined',
                    1: 'Manual', 2: 'Normal Program',
                    3: 'Aperture Priority', 4: 'Shutter Priority',
                    5: 'Creative Program',  6: 'Action Program',
                    7: 'Portrait Mode',     8: 'Landscape Mode'
                }},
            0x8824: {'name': 'SpectralSensitivity'},
            0x8827: {'name': 'ISOSpeedRatings'},
            0x8828: {'name': 'OECF'},
            0x829a: {'name': 'ExposureTime'},
            0x829d: {'name': 'FNumber'},
            0x9000: {'name': 'Exif/Version', 'func': __exifGetString},
            0xa000: {'name': 'FlashpixVersion', 'func': __exifGetString},
            0x9101: {
                'name': 'ComponentsConfiguration',
                'maps': {
                    0: 'Not Exist',
                    1: 'Y', 2: 'Cb', 3: 'Cr',
                    4: 'R', 5: 'G', 6: 'B'
                },
                'func': __exitComponentConfiguration
            },
            0x9102: {'name': 'CompressedBitsPerpixel'},
            0x9003: {'name': 'DateTimeOriginal'},
            0x9004: {'name': 'DateTimeDigitized'},
            0x9201: {'name': 'ShutterSpeedValue'},
            0x9202: {'name': 'ApertureValue'},
            0x9203: {'name': 'BrightnessValue'},
            0x9204: {'name': 'ExposureBiasValue'},
            0x9205: {'name': 'MaxApertureValue'},
            0x9206: {'name': 'SubjectDistance'},
            0x9207: {
                'name': 'MeteringMode',
                'maps': {
                    0: 'unknown',
                    1: 'Averge',  2: 'CenterWeightedAverage',
                    3: 'Spot',    4: 'MultiSpot',
                    5: 'Pattern', 6: 'Partial',
                    0xff: 'other'
                }},
            0x9208: {
                'name': 'LightSource',
                'maps': {
                    0: 'unknown',
                    1: 'Daylight',  2: 'Fluorescent',
                    3: 'Tungsten',  4: 'Flash',
                    9: 'Fine weather', 10: 'Cloudy',
                    11: 'Shade',       12: 'Daylight fluorescent',
                    13: 'Day white fluorescent',
                    14: 'Cool white fluorescent',
                    15: 'White fluorescent',
                    17: 'Standard light A',
                    18: 'Standard light B',
                    19: 'Standard light C',
                    20: 'D55', 21: 'D65', 22: 'D75', 23: 'D50',
                    24: 'ISO studio Tungsten',
                    0xff: 'other light source'
                }},
            0x9209: {
                'name': 'Flash',
                'maps': {
                    0: 'Not Fired',
                    1: 'Fired',
                    5: 'Strobe return light not detected',
                    7: 'strobe return light detected',
                    9: 'Fired, compulsory flash mode',
                    13: 'Fired, compulsory flash mode, '
                        'return light not detected',
                    15: 'Fired, compulsory flash mode, return light detected',
                    16: 'Not Fired, compulsory flash mode',
                    24: 'Not Fired, auto mode',
                    25: 'Fired, auto mode',
                    29: 'Fired, auto mode, return light not detected',
                    31: 'Fired, auto mode, return light detected',
                    32: 'No flash function',
                    65: 'Fired, red-eye reduction mode',
                    69: 'Fired, red-eye reduction mode, '
                        'return light not detected',
                    71: 'Fired, red-eye reduction mode, return light detected',
                    73: 'Fired, compulsory flash mode, red-eye reduction mode',
                    77: 'Fired, compulsory flash mode, red-eye reduction '
                        'mode, return light not detected',
                    78: 'Fired, compulsory flash mode, red-eye reduction '
                        'mode, return light detected',
                    89: 'Fired, auto mode, red-eye reduction mode',
                    93: 'Fired, auto mode, return light not detected, '
                        'red-eye reduction mode',
                    95: 'Fired, auto mode, return light detected, '
                        'red-eye reduction mode'}},
            0x920a: {'name': 'FocalLength'},
            0x9214: {'name': 'SubjectArea'},
            0x927c: {'name': 'MakerNote'},
            0x9286: {'name': 'UserComment'},
            0x9290: {'name': 'SubSecTime', 'func': __exifGetString},
            0x9291: {'name': 'SubSecTimeOriginal', 'func': __exifGetString},
            0x9292: {'name': 'SubSecTimeDigitized', 'func': __exifGetString},
            0xa001: {
                'name': 'ClorSpace',
                'maps': {
                    1: 'sRGB',
                    0xffff: 'uncalibrated'
                }},
            0xa002: {'name': 'PixelXDimension'},
            0xa003: {'name': 'PixelYDimension'},
            0xa004: {'name': 'RelatedSoundFile'},
            0xa005: {
                'name': 'Interoperability IDF',
                'ifd': {
                    0x01: {
                        'name': 'interoperabilityIndex',
                        'func': __exifGetString
                    },
                    0x02: {
                        'name': 'interoperabilityVersion',
                        'func': __exifGetString
                    }
                }
            },
            0xa20b: {'name': 'FlashEnergy'},
            0xa20c: {'name': 'SpatialFrequencyResponse'},
            0xa20e: {'name': 'FocalPlaneXResolution'},
            0xa20f: {'name': 'FocalPlaneYResolution'},
            0xa210: {'name': 'FocalPlaneResolutionUnit'},
            0xa214: {'name': 'SubjectLocation'},
            0xa215: {'name': 'ExposureIndex'},
            0xa217: {'name': 'SensingMethod'},
            0xa300: {'name': 'FileSource'},
            0xa301: {'name': 'SceneType'},
            0xa302: {'name': 'CFAPattern'},
            0xa401: {'name': 'CustomRendered'},
            0xa402: {'name': 'ExposureMode'},
            0xa403: {'name': 'WhiteBalance'},
            0xa404: {'name': 'DigitalZoomRatio'},
            0xa405: {'name': 'FocallengthIn35mmFilm'},
            0xa406: {'name': 'SceneCaptureType'},
            0xa407: {'name': 'GainControl'},
            0xa408: {'name': 'Contrast'},
            0xa409: {'name': 'Saturation'},
            0xa40a: {
                'name': 'Sharpness',
                'maps': {
                    0: 'Normal',
                    1: 'Soft',
                    2: 'Hard'
                }},
            0xa40b: {'name': 'DeviceSettingDescription'},
            0xa40c: {'name': 'SubjectDistanceRange'},
            0xa420: {'name': 'ImageUniqueID'}
        }},
    0x8825: {
        'name': 'GPS IFD',
        'ifd': {
            0x00: {'name': 'GpsVersionID'},
            0x01: {'name': 'GpsLatitudeRef'},
            0x02: {'name': 'GpsLatitude'},
            0x03: {'name': 'GpsLongitudeRef'},
            0x04: {'name': 'GpsLogitude'},
            0x05: {'name': 'GpsAltitudeRef'},
            0x06: {'name': 'GpsAltitude'},
            0x07: {'name': 'GpsTimeStampe'},
            0x08: {'name': 'GpsSatellites'},
            0x09: {'name': 'GpsStatus'},
            0x0a: {'name': 'GpsMeasureMode'},
            0x0b: {'name': 'GpsDOP'},
            0x0c: {'name': 'GpsSpeedRef'},
            0x0d: {'name': 'GpsSpeed'},
            0x0e: {'name': 'GpsTrackRef'},
            0x0f: {'name': 'GpsTrack'},
            0x10: {'name': 'GpsImgDirectionRef'},
            0x11: {'name': 'GpsImgDirection'},
            0x12: {'name': 'GpsMapDatum'},
            0x13: {'name': 'GpsDestLatitudeRef'},
            0x14: {'name': 'GpsDestLatitude'},
            0x15: {'name': 'GpsDestLongitudeRef'},
            0x16: {'name': 'GpsDestLogitude'},
            0x17: {'name': 'GpsDestBearingRef'},
            0x18: {'name': 'GpsDestBearing'},
            0x19: {'name': 'GpsDestDistanceRef'},
            0x1a: {'name': 'GpsDestDistance'},
            0x1b: {'name': 'GpsProcessingMethod'},
            0x1c: {'name': 'GpsAreaInformation'},
            0x1d: {'name': 'GpsDataStamp'},
            0x1e: {'name': 'GpsDiffferential'}
        }}
}


# 4.6.2 IFD Structure
def __exifFmtParse(tp, target, source, size, off=None, **kws):
    def _trimZero(a, b):
        while int(a / 10) * 10 == a and int(b / 10) * 10 == b:
            a /= 10
            b /= 10

        return "%d/%d" % (a, b)

    # add the length of Exif header
    if off is not None:
        kws['offset'] = off

    if tp == 1:  # BYTE
        ret = source.read(1)
        size_ = 1
    elif tp == 2:  # ASCII
        ret = target.readString(source, size, **kws)
        size_ = len(ret) + 1
    elif tp == 3:  # SHORT
        ret = target.readValue(source, 2, **kws)
        size_ = 2
    elif tp == 4:  # LONG
        ret = target.readValue(source, 4, **kws)
        size_ = 4
    elif tp == 5:  # RATIONAL
        a = target.readValue(source, 4, **kws)
        kws['offset'] += 4
        b = target.readValue(source, 4, **kws)
        size_, ret = 8, _trimZero(a, b)
    elif tp == 7:  # UNDEFINED
        ret = target.readString(source, size, **kws)
        size_ = size
    elif tp == 9:  # SLONG
        ret = target.readValue(source, 4, **kws)
        size_ = 4
    elif tp == 10:  # SRATIONAL
        a = target.readValue(source, 4, **kws)
        kws['offset'] += 4
        b = target.readValue(source, 4, **kws)
        size_, ret = 8, _trimZero(a, b)
    else:
        size_, ret = 0, ''

    return size_, str(ret)


def __newItemObject(target, subject, content, size, off=0, **kws):
    if off != 0:
        objectp = target.newItemObject(
            subject, content,
            field=target.newFieldObject(kws['start'] + off, size),
            attribute=target.newAttributionObject('simple'))
    else:
        objectp = target.newItemObject(
            subject, content,
            field=target.newFieldObject(kws['start'] + kws['offset'], size),
            attribute=target.newAttributionObject('simple'))

    return objectp


def _exifDecodeIfds(k, entry, target, source, tags=_EXIT_TAGS, **kws):

    _EXCLUDED_TYPE = (1, 3, 4, 9)
    _LEAD_HEADER = (
        ('nb', 2, 'decimal,export'),
    )

    if k is not None:
        nsub = 'IFD #%d' % k
    else:
        nsub = 'IFD'

    ret, objectp, consumed, _, exported = target.parse(
        nsub, _LEAD_HEADER, afile=source, attribute='simple', **kws)

    nb = 0
    if ret:
        nb = exported[0]
        kws['offset'] += consumed
        entry.appendChildren(objectp)

    def __tag(tags_, tag, **kws):
        if tag > 0xff:
            title = '%s (0x%04x)'
        else:
            title = '%s (0x%02x)'

        ta_ = tags_.get(tag)
        if ta_ is not None:
            return title % (ta_.get('name'), tag)
        else:
            return 'Unknown'

    for i in xrange(nb):
        _ENTRY_HEADER = (
            ('tag', 2, 'value,export'),
            ('type', 2, 'value,export'),
            ('count', 4, 'decimal,export')
        )

        ret, obj, consumed, _, exported = target.parse(
            '#%d' % i, _ENTRY_HEADER, afile=source, attribute='simple', **kws)

        if ret:
            kws['offset'] += consumed
            # obj.adjust(size=4)
            objectp.appendChildren(obj)

            ta, tp, length = exported
            ta_ = tags.get(ta, '')

            offset = target.readValue(source, 4, **kws)
            if 'func' in ta_:
                data, size, objp = ta_.get('func')(
                    ta_, tp, target, source, length, **kws)

                obj.appendChildren(objp)
            elif tp in _EXCLUDED_TYPE:
                size, data = __exifFmtParse(
                    tp, target, source, length, **kws)
            else:
                size, data = __exifFmtParse(
                    tp, target, source, length,
                    offset + _EXIF_HEADER_LENGTH, **kws)

                obj.appendChildren(
                    __newItemObject(target, 'offset', offset, 4, **kws))

            if 'ifd' in ta_:
                obj.update(subject='#%d' % i,
                           content='%s' % (__tag(tags, ta, **kws)))

                offset = kws['offset']
                kws['offset'] = int(data) + _EXIF_HEADER_LENGTH
                ret = _exifDecodeIfds(None, obj, target, source,
                                      tags=ta_.get('ifd'), **kws)
            elif 'maps' in ta_ and 'func' not in ta_:
                maps = ta_.get('maps')
                try:
                    va = maps.get(int(data), 'reserved')
                except ValueError:
                    # TODO: dig into analyze the error
                    va = '<ERROR>'

                obj.appendChildren(
                    __newItemObject(
                        target, 'value', '%s (%s)' % (va, data),
                        size, **kws))

                obj.update(
                    subject='#%d' % i,
                    content='%s - %s' % (__tag(tags, ta, **kws), va))
            else:
                if tp in _EXCLUDED_TYPE:
                    obj.appendChildren(
                        __newItemObject(
                            target, 'value', data,
                            size, **kws))
                elif 'func' not in ta_:
                    obj.appendChildren(
                        __newItemObject(
                            target, 'data', data,
                            size, offset + _EXIF_HEADER_LENGTH, **kws))

                obj.update(
                    subject='#%d' % i,
                    content='%s - %s' % (__tag(tags, ta, **kws), data))

            # add the size of "Value Offset"
            obj.adjust(size=obj.getSize() + 4)
            kws['offset'] += 4

        if not ret:
            break

    ifd = 0
    if ret:
        ifd = target.readValue(source, 4, **kws)
        objectp.appendChildren(__newItemObject(target, 'Next IFD',
                               ifd, 4, **kws))

    return ifd


def _exifPduReco(entry, target, source=None, data=None, **kws):

    def _order(_, data):
        if data == 'II':
            ret = 'Intel'
            target.setEndian('little-endian', kws)
        else:
            ret = 'Motorola'
            target.setEndian('big-endian', kws)

        return '%s (%s)' % (ret, data)

    _EXIT_HEADER = (
        ('Signature', 6, 'string,verify', {'verified': 'Exif\x00\x00'}),
        ('TIFF Header', 8, 'recursive', {
            'subdescriptor': (
                ('Order', 2, 'string', {'func': _order}),
                ('Tag', 2, 'value'),
                ('0th IFD', 4, 'value,export')
            )})
    )

    if source is None:
        source = target.newFile(data=data)

    endian = target.getEndian(kws)
    start = kws.get('start', 0) + kws.get('offset', 0)
    kws['start'] = kws['offset'] = 0

    ret, objectp, consumed, _, exported = target.parse(
        'Exif', _EXIT_HEADER, afile=source, attribute='simple', **kws)

    if ret:
        # use the correct endian to analyze again
        ret, objectp, consumed, _, exported = target.parse(
            'Exif', _EXIT_HEADER, afile=source, attribute='simple', **kws)

    if ret:
        k = 0
        ifd = exported[0]
        kws['offset'] += consumed
        while ifd:
            # size '6' is the signature 'Exif\x00\x00' length
            kws['offset'] = ifd + _EXIF_HEADER_LENGTH
            ifd = _exifDecodeIfds(k, objectp, target, source, **kws)
            k += 1

        objectp.adjust(start=start, recursive=True)

    target.setEndian(endian, kws)
    return objectp


class _ExifProvider:
    __referral__ = (('decode', _exifPduReco),)

    def Execute(self, source, target, notify, **kws):
        entry = target.getEntry()
        return _exifPduReco(entry, target, source, **kws)


def prvService():
    return _ExifProvider
